using Newtonsoft.Json;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Threading.Tasks;

namespace Infrastructure.Redis
{
    /// <summary>
    /// Redis实例
    /// </summary>
    public class RedisInstance
    {
        #region Ctor(s）

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="name">示例名称</param>
        /// <param name="conStr">连接字符串</param>     
        /// <param name="ConnectTimeout">连接过期时间</param>
        /// <param name="SyncTimeout">同步过期时间</param>
        /// <param name="ResponseTimeout">响应过期时间</param>
        /// <param name="db">数据库Id</param>
        public RedisInstance(string name, string conStr,int ConnectTimeout=15000, int SyncTimeout = 5000, int ResponseTimeout = 15000, int db = -1)
        {
            Name = name;
            ConStr = conStr;
            Db = db;

            try
            {
                var config = new ConfigurationOptions
                {
                    AbortOnConnectFail = false,
                    AllowAdmin = true,
                    ConnectTimeout = ConnectTimeout,
                    SyncTimeout = SyncTimeout,
                    ResponseTimeout = ResponseTimeout,
                    EndPoints = { ConStr }
                };

                Connection = ConnectionMultiplexer.Connect(config);
            }
            catch (Exception e)
            {
#pragma warning disable CS4014 // 由于此调用不会等待，因此在调用完成前将继续执行当前方法
                LogHelper.Error($"Connect to redis server '{conStr}' faild.", e);
#pragma warning restore CS4014 // 由于此调用不会等待，因此在调用完成前将继续执行当前方法
                return;
            }

            // 获取数据库
            if (Connection.IsConnected)
            {
                Database = Connection.GetDatabase(db);
            }
        }
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="name">示例名称</param>
        /// <param name="conStr">连接字符串</param>
        /// <param name="db">数据库Id</param>
        public RedisInstance(string name, string conStr, int db = -1)
        {
            Name = name;
            ConStr = conStr;
            Db = db;

            try
            {
                Connection = ConnectionMultiplexer.Connect(conStr);
            }
            catch (Exception e)
            {
#pragma warning disable CS4014 // 由于此调用不会等待，因此在调用完成前将继续执行当前方法
                LogHelper.Error($"Connect to redis server '{conStr}' faild.", e);
#pragma warning restore CS4014 // 由于此调用不会等待，因此在调用完成前将继续执行当前方法
                return;
            }

            // 获取数据库
            if (Connection.IsConnected)
            {
                Database = Connection.GetDatabase(db);
            }
        }

        #endregion

        /// <summary>
        /// 设置字符串值。
        /// 如果键已存在,则更新键的值。
        /// </summary>
        /// <param name="key">指定的键</param>
        /// <param name="value">字符串值</param>
        /// <param name="expiry">有效期</param>
        /// <returns>设置成功返回true,设置失败或value为空返回false。</returns>
        public bool StringSet(string key, string value, TimeSpan? expiry = null)
        {
            // 值为空
            if (string.IsNullOrEmpty(value))
            {
                return false;
            }

            // 未连接
            if (Connection == null || !Connection.IsConnected)
            {
                // 重连
                Reconnect();
            }

            // 设置字符串值
            return Database.StringSet(key, value, expiry);
        }

        /// <summary>
        /// 批量设置字符串值。
        /// 如果键已存在,则更新键的值。
        /// 键值集合中键为空的键值对将跳过。
        /// </summary>
        /// <param name="values">指定的键值集合</param>
        /// <returns>设置成功返回true,设置失败或values为空返回false。</returns>
        public bool StringSet(IEnumerable<KeyValuePair<string, string>> values)
        {
            // 键值集合为空
            if (values == null || values.Count() == 0)
            {
                return false;
            }

            // 未连接
            if (Connection == null || !Connection.IsConnected)
            {
                // 重连
                Reconnect();
            }

            List<KeyValuePair<RedisKey, RedisValue>> valueList = new List<KeyValuePair<RedisKey, RedisValue>>();

            // 排除key为空的数据
            foreach (var item in values)
            {
                if (string.IsNullOrEmpty(item.Key)) continue;

                valueList.Add(new KeyValuePair<RedisKey, RedisValue>(item.Key, item.Value));
            }

            // 设置字符串值
            return Database.StringSet(valueList.ToArray());
        }

        /// <summary>
        /// 根据键获取字符串值
        /// </summary>
        /// <param name="key">指定的键</param>
        /// <returns>键的值</returns>
        public string StringGet(string key)
        {
            // 未连接
            if (Connection == null || !Connection.IsConnected)
            {
                // 重连
                Reconnect();
            }

            // 获取字符串值
            var redisValue = Database.StringGet(key);

            // 无值
            if (!redisValue.HasValue)
            {
                return string.Empty;
            }

            return redisValue.ToString();
        }

        /// <summary>
        /// 获取多个键的字符串值。
        /// 值为空的键将不会执行读取。
        /// 无值时将返回一个无数据的 Dictionary 。
        /// </summary>
        /// <param name="keys">指定的键集合</param>
        /// <returns>键-值集合。</returns>
        public Dictionary<string, string> StringGet(IEnumerable<string> keys)
        {
            Dictionary<string, string> dict = new Dictionary<string, string>();

            // 键值集合为空
            if (keys == null || keys.Count() == 0)
            {
                return dict;
            }

            // 未连接
            if (Connection == null || !Connection.IsConnected)
            {
                // 重连
                Reconnect();
            }

            List<RedisKey> keyList = new List<RedisKey>();

            // 排除key为空的数据
            foreach (var item in keys)
            {
                if (string.IsNullOrEmpty(item)) continue;

                keyList.Add(item);
            }

            // 获取字符串值
            var redisValues = Database.StringGet(keyList.ToArray());

            // 无值
            if (redisValues == null || redisValues.Length == 0)
            {
                return dict;
            }

            for (int i = 0; i < redisValues.Length; i++)
            {
                if (redisValues[i].HasValue)
                {
                    dict.Add(keys.ElementAt(i), redisValues[i]);
                }
                else
                {
                    dict.Add(keys.ElementAt(i), string.Empty);
                }
            }

            return dict;
        }

        /// <summary>
        /// 设置Hash值。
        /// 如果键已存在,则更新键的值。
        /// 如果键不存在,则新增键。
        /// </summary>
        /// <param name="key">指定的键</param>
        /// <param name="filed">指定的字段</param>
        /// <param name="data">字段的数据</param>
        /// <returns>设置成功返回true,设置失败或data为空返回false。</returns>
        public bool HashSet(string key, string filed, object data)
        {
            // 值为空
            if (data == null)
            {
                return false;
            }

            // 未连接
            if (Connection == null || !Connection.IsConnected)
            {
                // 重连
                Reconnect();
            }

            // 设置字符串值
            string value = data is string ? data.ToString() : data.Serialize();

            return Database.HashSet(key, filed, value);

        }

        /// <summary>
        /// 获取Hash对象
        /// </summary>
        /// <param name="key">指定的键</param>
        /// <returns>key指定的Hash对象</returns>
        public List<HashEntry> HashGetAll(string key)
        {
            // 未连接
            if (Connection == null || !Connection.IsConnected)
            {
                // 重连
                Reconnect();
            }

            // 获取Hash值
            var hashEntry = Database.HashGetAll(key);

            // 无值
            if (hashEntry == null)
            {
                return new List<HashEntry>();
            }

            return hashEntry.ToList();
        }

        /// <summary>
        /// 获取指定条数Hash对象 
        /// </summary>
        /// <param name="key">指定的键</param>
        /// <param name="page">获取条数</param>
        /// <returns>key指定的Hash对象</returns>
        public IEnumerable<HashEntry> HashScan(string key, int page)
        {
            // 未连接
            if (Connection == null || !Connection.IsConnected)
            {
                // 重连
                Reconnect();
            }
            // 获取Hash值
            var hashEntry = Database.HashScan(key, RedisValue.EmptyString, page).Take(page);


            // 无值
            if (hashEntry == null)
            {
                return new List<HashEntry>();
            }

            return hashEntry;
        }

        /// <summary>
        /// 获取指定Hash对象 
        /// </summary>
        /// <param name="key">指定的键</param>
        /// <param name="field">指定的Key</param>
        /// <returns>key指定的Hash对象</returns>
        public RedisValue HashGet(string key, string field)
        {
            // 未连接
            if (Connection == null || !Connection.IsConnected)
            {
                // 重连
                Reconnect();
            }
            // 获取Hash值
            return Database.HashGetAsync(key, field).Result;
        }

        /// <summary>
        /// 移除Hash中的字段
        /// </summary>
        /// <param name="key">指定的键</param>
        /// <param name="hashField">需要移除的字段</param>
        /// <returns>移除了字段返回true,移除失败或不存在指定键或字段返回false</returns>
        public bool HashDelete(string key, string hashField)
        {
            // 未连接
            if (Connection == null || !Connection.IsConnected)
            {
                // 重连
                Reconnect();
            }

            return Database.HashDelete(key, hashField);
        }

        /// <summary>
        /// 验证指定的键是否存在
        /// </summary>
        /// <param name="key">指定的键</param>
        /// <returns>指定的键存在返回true,否则返回false</returns>
        public bool KeyExists(string key)
        {
            // 未连接
            if (Connection == null || !Connection.IsConnected)
            {
                // 重连
                Reconnect();
            }

            return Database.KeyExists(key);
        }

        /// <summary>
        /// 删除指定的键
        /// </summary>
        /// <param name="key">指定的键</param>
        /// <returns>删除成功返回true,否则返回false</returns>
        public bool KeyDelete(string key)
        {
            // 未连接
            if (Connection == null || !Connection.IsConnected)
            {
                // 重连
                Reconnect();
            }

            return Database.KeyDelete(key);
        }

        /// <summary>
        /// 选择数据库
        /// </summary>
        /// <param name="db">指定数据库Id</param>
        public void SwitchDatabase(int db)
        {
            // 未连接
            if (Connection == null || !Connection.IsConnected)
            {
                // 重连
                Reconnect();
            }

            Db = db;
            Database = Connection.GetDatabase(Db);
        }

        #region Private method(s)

        /// <summary>
        /// 重连Redis
        /// </summary>
        private void Reconnect()
        {
            // 已连接
            if (Connection != null && Connection.IsConnected)
            {
                return;
            }

            try
            {
                Connection = ConnectionMultiplexer.Connect(ConStr);
            }
            catch (Exception e)
            {
#pragma warning disable CS4014 // 由于此调用不会等待，因此在调用完成前将继续执行当前方法
                LogHelper.Error($"Reconnect to redis server '{ConStr}' failed.", e);
#pragma warning restore CS4014 // 由于此调用不会等待，因此在调用完成前将继续执行当前方法
                throw e;
            }

            // 获取数据库
            if (Connection.IsConnected)
            {
                Database = Connection.GetDatabase(Db);
            }
        }

        #endregion

        #region Properties

        /// <summary>
        /// 实例名称
        /// </summary>
        public string Name;

        private IConnectionMultiplexer Connection;

        private IDatabase Database;

        private string ConStr;

        private int Db;

        #endregion

    }

}
